use std::collections::HashMap;

pub enum Expression {
  Atom(char),
  Operation(char, Vec<Expression>),
}

impl std::fmt::Display for Expression {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      Expression::Atom(i) => write!(f, "{}", i),
      Expression::Operation(head, rest) => {
        write!(f, "({}", head)?;
        for s in rest {
          write!(f, " {}", s)?
        }
        write!(f, ")")
      }
    }
  }
}

impl Expression {
  pub fn is_asign(&self) -> Option<(char, &Expression)> {
    match self {
      Expression::Atom(_) => None,
      Expression::Operation(c, operands) => {
        if *c == '=' {
          let var_name = match operands.first().unwrap() {
            Expression::Atom(c) => {
              if *c >= 'a' && *c <= 'z' || *c >= 'A' && *c <= 'Z' {
                *c
              } else {
                panic!("Not a variable name: {}", c)
              }
            }
            _ => unreachable!(),
          };
          return Some((var_name, operands.last().unwrap()));
        }
        None
      }
    }
  }

  pub fn eval(&self, variables: &HashMap<char, f32>) -> f32 {
    match self {
      Expression::Atom(c) => match c {
        '0'..='9' => c.to_digit(10).unwrap() as f32,
        'a'..='z' | 'A'..='Z' => *variables
          .get(c)
          .expect("Undefined variable {c}"),
        _ => unreachable!(),
      },
      Expression::Operation(operator, operands) => {
        let lhs = operands.first().unwrap().eval(variables);
        let rhs = operands.last().unwrap().eval(variables);
        match operator {
          '+' => lhs + rhs,
          '-' => lhs - rhs,
          '*' => lhs * rhs,
          '/' => lhs / rhs,
          '^' => lhs.powf(rhs),
          '√' => lhs.powf(1.0 / (rhs)),
          op => panic!("Unknown operator: {}", op),
        }
      }
    }
  }
}


